#include "TextInput.h"

namespace air
{
	TextInput::TextInput(
		float _x, float _y, float _width, float _height,
		hgeFont *_fnt, DWORD _color, int _capacity, const char *_text)
		: fnt(_fnt), capacity(_capacity), isMultiLine(false), enterHook(0),
		focused(false), maxSize(9999999), blinkCounter(0), tabInsert(false)
	{
		rect = new Rect(_x, _y, _x + _width, _y + _height);

		// style
		color.SetHWColor(_color);
		borderColorNorm.SetHWColor(0xFFFFFFFF);		// default white
		borderColorOver.SetHWColor(0xFFFFFF00);
		borderColorFocused.SetHWColor(0xFF00FFFF);
		borderColor = borderColorNorm;
		padTop      = 1.0f;
		padLeft     = 3.0f;
		cursorXOffset = 0.07f;
		cursorYOffset = 0.0f;
		blinkInterval = 0.62f;

		text = new char[capacity + 1];
		SetText(_text);
		pos = size;					// size will be initialized in SetText()

		updateCursorPos();
	}
	TextInput::~TextInput()
	{
		delete [] text;
	}

	bool TextInput::KeyClick(int key, int chr)
	{
		if (
			key >= HGEK_0 && key <= HGEK_9
			|| key >= HGEK_A && key <= HGEK_Z
			|| key == HGEK_GRAVE
			|| key == HGEK_MINUS
			|| key == HGEK_EQUALS
			|| key == HGEK_BACKSLASH
			|| key == HGEK_LBRACKET
			|| key == HGEK_RBRACKET
			|| key == HGEK_SEMICOLON
			|| key == HGEK_APOSTROPHE
			|| key == HGEK_COMMA
			|| key == HGEK_PERIOD
			|| key == HGEK_SLASH
			|| key == HGEK_SPACE
			) {
				insertChr(chr);
		}
		else if (key == HGEK_TAB && tabInsert) {
			// the font doesn't have tab char
			insertChr(' ');
			insertChr(' ');
			insertChr(' ');
			insertChr(' ');
		}
		else if (key == HGEK_ENTER) {
			if (isMultiLine)
				insertChr('\n');
			else if (enterHook)		// test for zero
				enterHook();
		}
		else if (key == HGEK_LEFT)
			backwardChar();
		else if (key == HGEK_RIGHT)
			forwardChar();
		else if (key == HGEK_BACKSPACE)
			backspace();
		else if (key == HGEK_DELETE)
			deleteChar();
		updateCursorPos();			// TODO: put it here?
		return true;
	}

	bool TextInput::SetText(const char *_text)
	{
		// TODO: use strlen and expand to get enough capacity first may
		// be better
		for (size = 0; _text[size] != 0; ++size)
		{
			if (size > capacity && !expand())		// shortcut eval here
				return false;
			if (_text[size] == '\n')
				isMultiLine = true;
			text[size] = _text[size];
		}
		text[size] = 0;
		return true;
	}

	bool TextInput::insertChr(int chr)
	{
		if (size == maxSize)
			return false;
		if (size == capacity) {
			if (!expand())
				return false;
		}
		for (int i = size; i > pos; --i)
			text[i] = text[i - 1];
		text[pos++] = chr;
		text[++size] = 0;
		return true;
	}

	bool TextInput::backspace()
	{
		if (pos == 0) return false;
		strcpy(text + pos - 1, text + pos);
		--pos;
		text[--size] = 0;
		return true;
	}
	bool TextInput::backwardChar()
	{
		if (pos == 0) return false;
		--pos;
		return true;
	}
	bool TextInput::forwardChar()
	{
		if (pos == size) return false;
		++pos;
		return true;
	}
	bool TextInput::deleteChar()
	{
		if (pos == size) return false;
		++pos;
		return backspace();
	}

	bool TextInput::expand()
	{
		capacity *= 2;
		char *textNew;
		try {
			textNew = new char[capacity + 1];
		} catch (...) {
			capacity /= 2;
			return false;
		}
		strcpy(textNew, text);
		delete [] text;
		text = textNew;
		return true;
	}
	void TextInput::updateCursorPos()
	{
		blinkCounter = 0;				// do not blink while moving

		cursorX = GetX() + padLeft + cursorXOffset;
		cursorY = GetY() + padTop + cursorYOffset;

		if (!isMultiLine) {
			cursorX += fnt->GetStringWidth(text)
				- fnt->GetStringWidth(text + pos);
		} else {
			int line = 0;
			int nlPos = -1;		// last newline position
			int i;
			for (i = 0; i < pos; ++i) {
				if (text[i] == '\n') {
					++line;
					nlPos = i;
				}
			}
			cursorX += fnt->GetStringWidth(text + nlPos + 1, false)
				- fnt->GetStringWidth(text + pos, false);
			cursorY += line * fnt->GetHeight();
		}
	}

	//----8<-----------8<------------8<---------------
	TextInputHgeObject::TextInputHgeObject(TextInput* textInput)
	{
		tiObj = textInput;
		fnt   = textInput->fnt;
	}
	TextInputHgeObject::~TextInputHgeObject()
	{ }
	bool TextInputHgeObject::initialize(Graphic* graphic)
	{
		hge = ((HgeGraphic*)graphic)->getHge();
		return true;
	}
	void TextInputHgeObject::render()
	{
		// render the border
		hge->Gfx_RenderLine(tiObj->GetX(), tiObj->GetY(), tiObj->GetX2(), tiObj->GetY(), tiObj->borderColor.GetHWColor());
		hge->Gfx_RenderLine(tiObj->GetX2(), tiObj->GetY(), tiObj->GetX2(), tiObj->GetY2(), tiObj->borderColor.GetHWColor());
		hge->Gfx_RenderLine(tiObj->GetX2(), tiObj->GetY2(), tiObj->GetX(), tiObj->GetY2(), tiObj->borderColor.GetHWColor());
		hge->Gfx_RenderLine(tiObj->GetX(), tiObj->GetY2(), tiObj->GetX(), tiObj->GetY(), tiObj->borderColor.GetHWColor());
		// render a cursor beam
		if (tiObj->focused && tiObj->blinkCounter < tiObj->blinkInterval)
			hge->Gfx_RenderLine(tiObj->cursorX, tiObj->cursorY, tiObj->cursorX,
			tiObj->cursorY + fnt->GetHeight(), tiObj->color.GetHWColor());
		// render the text
		fnt->SetColor(tiObj->color.GetHWColor());
		fnt->Render(tiObj->rect->getPositionX() + tiObj->padLeft, tiObj->rect->getPositionY() + tiObj->padTop, HGETEXT_LEFT, tiObj->text);

	}
}